#include <cmath>
#include <vector>

#include "BlobCollective.h"

namespace BlobSalladDS
{
	BlobCollective::BlobCollective(int x, int y, int startNum, int maxNum)
	{
		this->maxNum = maxNum;
		this->numActive = 1;
		this->blobs = new std::vector<Blob *>();
		this->tmpForce = new Vector(0.0, 0.0);
		this->selectedBlob = NULL;

		this->blobs->push_back(new Blob(x, y, 0.4, 8));
	}

	double BlobCollective::drand()
	{
		return rand() / RAND_MAX * 1.0;
	}

	void BlobCollective::split()
	{
		size_t i;
		double maxRadius = 0.0;
		size_t emptySlot;
		Blob *motherBlob, *newBlob;

		if (this->numActive == this->maxNum)
		{
			return;
		}

		emptySlot = this->blobs->size();

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (this->blobs->at(i) != NULL && this->blobs->at(i)->getRadius() > maxRadius)
			{
				maxRadius = this->blobs->at(i)->getRadius();
				motherBlob = this->blobs->at(i);
			}
			else if (this->blobs->at(i) == NULL)
			{
				emptySlot = i;
			}
		}

		motherBlob->scale(0.75);
		newBlob = new Blob(motherBlob->getXPos(), motherBlob->getYPos(), motherBlob->getRadius(), 8);

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (this->blobs->at(i) == NULL)
			{
				continue;
			}

			this->blobs->at(i)->addBlob(*newBlob);
			newBlob->addBlob(*(this->blobs->at(i)));
		}

		this->blobs->at(emptySlot) = newBlob;

		this->numActive++;
	}

	int BlobCollective::findSmallest(int exclude)
	{
		double minRadius = 100.0;
		int minIndex = 0;
		int i;

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (i == exclude || this->blobs->at(i) == NULL)
			{
				continue;
			}

			if (this->blobs->at(i)->getRadius() < minRadius)
			{
				minIndex = i;
				minRadius = this->blobs->at(i)->getRadius();
			}
		}

		return minIndex;
	}

	int BlobCollective::findClosest(int exclude)
	{
		int foundIndex = 0;
		double minDist = 1000.0, dist, aXbX, aYbY;
		int i;
		PointMass *myPointMass, *otherPointMass;

		myPointMass = this->blobs->at(exclude)->getMiddlePointMass();

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (i == exclude || this->blobs->at(i) == NULL)
			{
				continue;
			}

			otherPointMass = this->blobs->at(i)->getMiddlePointMass();
			aXbX = myPointMass->getXPos() - otherPointMass->getXPos();
			aYbY = myPointMass->getYPos() - otherPointMass->getYPos();
			dist = aXbX * aXbX + aYbY * aYbY;

			if (dist < minDist)
			{
				minDist = dist;
				foundIndex = i;
			}
		}

		return foundIndex;
	}

	void BlobCollective::join()
	{
		int blob1Index, blob2Index;
		double r1, r2, r3;

		if (this->numActive == 1)
		{
			return;
		}

		blob1Index = this->findSmallest(-1);
		blob2Index = this->findClosest(blob1Index);

		r1 = this->blobs->at(blob1Index)->getRadius();
		r2 = this->blobs->at(blob2Index)->getRadius();
		r3 = sqrt(r1 * r1 + r2 * r2);

		this->blobs->at(blob1Index) = NULL;
		this->blobs->at(blob2Index)->scale(0.945 * r3 / r2);

		this->numActive--;
	}

	POINT BlobCollective::selectBlob(int x, int y)
	{
		size_t i;
		double minDist = 10000.0;
		PointMass *otherPointMass;
		POINT selectOffset;

		if (this->selectedBlob != NULL)
		{
			return POINT(0, 0);
		}

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (this->blobs->at(i) == NULL)
			{
				continue;
			}

			otherPointMass = this->blobs->at(i)->getMiddlePointMass();
			double aXbX = x - otherPointMass->getXPos();
			double aYbY = y - otherPointMass->getYPos();
			double dist = aXbX * aXbX + aYbY * aYbY;

			if (dist < minDist)
			{
				minDist = dist;

				if (dist < this->blobs->at(i)->getRadius() * 0.5)
				{
					this->selectedBlob = this->blobs->at(i);
					selectOffset = POINT(aXbX, aYbY);
				}
			}
		}

		if (this->selectedBlob != NULL)
		{
			this->selectedBlob->setSelected(true);
		}

		return selectOffset;
	}

	void BlobCollective::unselectBlob()
	{
		if (this->selectedBlob == NULL)
		{
			return;
		}

		this->selectedBlob->setSelected(false);
		this->selectedBlob = NULL;
	}

	void BlobCollective::selectedBlobMoveTo(int x, int y)
	{
		if (this->selectedBlob == NULL)
		{
			return;
		}

		this->selectedBlob->moveTo(x, y);
	}

	void BlobCollective::move(double dt)
	{
		size_t i;

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (this->blobs->at(i) == NULL)
			{
				continue;
			}

			this->blobs->at(i)->move(dt);
		}
	}

	void BlobCollective::sc(Environment &env)
	{
		size_t i;

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (this->blobs->at(i) == NULL)
			{
				continue;
			}

			this->blobs->at(i)->sc(env);
		}
	}

	void BlobCollective::setForce(Vector *force)
	{
		size_t i;

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (this->blobs->at(i) == NULL)
			{
				continue;
			}

			if (this->blobs->at(i) == this->selectedBlob)
			{
				this->blobs->at(i)->setForce(new Vector(0.0, 0.0));
				continue;
			}
			this->blobs->at(i)->setForce(force);
		}
	}

	void BlobCollective::addForce(Vector &force)
	{
		size_t i;

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (this->blobs->at(i) == NULL)
			{
				continue;
			}

			if (this->blobs->at(i) == this->selectedBlob)
			{
				continue;
			}

			srand(time(NULL));
			this->tmpForce->setX(force.getX() * (drand() * 0.75 + 0.25));
			this->tmpForce->setY(force.getY() * (drand() * 0.75 + 0.25));
			this->blobs->at(i)->addForce(this->tmpForce);
      }
	}

	void BlobCollective::draw(void * ctx, double scaleFactor)
	{
		size_t i;

		for (i = 0; i < this->blobs->size(); i++)
		{
			if (this->blobs->at(i) == NULL)
			{
				continue;
			}

			this->blobs->at(i)->draw(ctx, scaleFactor);
		}
	}

}
